﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Compression;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using Look_Through_Windows.Helper;
using Look_Through_Windows.Network;

namespace Look_Through_Windows.Logic
{
    /// <summary>
    /// A window that is shared from local pc (to peers)
    /// </summary>
    class SharedWindow
    {
        /// <summary>
        /// Shared HWND
        /// </summary>
        public IntPtr Hwnd;
        /// <summary>
        /// Name of that window
        /// </summary>
        public string Name;

        /// <summary>
        /// Set of receiving peers
        /// </summary>
        public HashSet<NetConnection> Peers = new HashSet<NetConnection>( );

        /// <summary>
        /// Last snapshot
        /// </summary>
        private IntPtr lastBuffer;
        /// <summary>
        /// Last width and height
        /// </summary>
        private int lastWidth, lastHeight;

        /// <summary>
        /// Acks needed
        /// </summary>
        private int AckNeeded = 0;

        /// <summary>
        /// Is this a window?
        /// </summary>
        public bool Valid { get { return WindowExtract.IsWindow( Hwnd ); } }

        /// <summary>
        /// Updates the name
        /// </summary>
        public void UpdateName( )
        {
            StringBuilder sb = new StringBuilder( ( 1 << 15 ) + 1 );
            WindowExtract.GetWindowText( Hwnd, sb, 1 << 15 );
            Name = sb.ToString( );
        }

        public SharedWindow( IntPtr hwnd )
        {
            Hwnd = hwnd;
            UpdateName( );
        }

        public override string ToString( )
        {
            return Name + " (" + Hwnd.ToInt32( ).ToString( "X" ) + ", " + Peers.Count + " Peers)";
        }

        /// <summary>
        /// Acks 
        /// </summary>
        public void Ack( NetConnection conn )
        {
            --AckNeeded;
            if ( AckNeeded < 0 ) throw new InvalidOperationException( );
        }

        /// <summary>
        /// Gets the client rect
        /// </summary>
        private RECT ClientRect
        {
            get
            {
                RECT r;
                RECT wr;
                WindowExtract.GetClientRect( Hwnd, out r );
                WindowExtract.GetWindowRect( Hwnd, out wr );
                System.Drawing.Point p = new System.Drawing.Point( 0, 0 );
                WindowExtract.ClientToScreen( Hwnd, ref p );
                int diffX = p.X - wr.Left;
                int diffY = p.Y - wr.Top;
                r.Left += diffX;
                r.Top += diffY;
                r.Right += diffX;
                r.Bottom += diffY;
                return r;
            }
        }

        /// <summary>
        /// Sends an update
        /// </summary>
        internal void Update( )
        {
            if ( AckNeeded > 2 ) return;

            if ( !WindowExtract.IsWindowVisible( Hwnd ) ) return;

            if ( !WindowExtract.IsWindow( Hwnd ) ) return;


            int width = -1, height = -1;
            IntPtr buffer = IntPtr.Zero;
            try
            {
                buffer = WindowExtract.ExtractAllocate( Hwnd, ref width, ref height );
            }
            catch ( SEHException ex )
            {
                Console.WriteLine( Hwnd.ToInt64( ).ToString( "X" ) + " (" + Name + ") produced an exception: " + ex.Message );
                return;
            }

            bool needsClear = width != lastWidth || height != lastHeight;

            IntPtr updateBuffer = buffer;
            bool disposeUpdateBuffer = false;

            int minx = lastWidth, maxx = 0, miny = lastHeight, maxy = 0;
            int[] diff_pixels = null;

            int changedPixels = lastWidth * lastHeight;

            if ( !needsClear )
            {
                disposeUpdateBuffer = true;
                updateBuffer = WindowExtract.XOR( buffer, lastBuffer, lastWidth * lastHeight );
                int[] pixels = new int[ lastWidth * lastHeight ];
                Marshal.Copy( updateBuffer, pixels, 0, lastWidth * lastHeight );
                int i = 0;
                changedPixels = 0;
                for ( int y = 0 ; y < lastHeight ; ++y )
                    for ( int x = 0 ; x < lastWidth ; ++x )
                        if ( pixels[ i++ ] != 0 )
                        {
                            minx = Math.Min( minx, x );
                            miny = Math.Min( miny, y );
                            maxx = Math.Max( maxx, x );
                            maxy = Math.Max( maxy, y );
                            ++changedPixels;
                        }
                ++maxx;
                ++maxy;

                if ( changedPixels == 0 )
                {
                    WindowExtract.ExtractDeallocate( buffer );
                    WindowExtract.ExtractDeallocate( updateBuffer );
                    return;
                }

                int uwidth = maxx - minx;
                int uheight = maxy - miny;
                diff_pixels = new int[ uwidth * uheight ];
                for ( int y = 0 ; y < uheight ; ++y )
                    for ( int x = 0 ; x < uwidth ; ++x )
                        diff_pixels[ y * uwidth + x ] = pixels[ ( y + miny ) * lastWidth + ( x + minx ) ];
            }

            if ( Peers.Count > 0 )
            {
                RECT r = ClientRect;

                bool clearcheck = true;

                NetMessage clearmsg = null;
                NetMessage msg = null;

                int sentPixel = width * height;
                if ( !needsClear )
                {
                    msg = new NetMessage( SendCodes.WindowContent );
                    msg.Write( Hwnd.ToInt64( ), "hwnd" );
                    msg.Write( false, "clear" );
                    msg.Write( r.Left, "left" );
                    msg.Write( r.Top, "top" );
                    msg.Write( r.Right, "right" );
                    msg.Write( r.Bottom, "bottom" );

                    Stopwatch sw = new Stopwatch( );
                    sw.Start( );
                    sentPixel = 0;
                    SegmentTree tree = SegmentTree.Create( diff_pixels, maxx - minx, maxy - miny );
                    List<SegmentTree> leaves = new List<SegmentTree>( tree );
                    msg.Write( leaves.Count, "count" );
                    foreach ( var leaf in leaves )
                    {
                        msg.Write( leaf.AABB.X + minx, "left-u" );
                        msg.Write( leaf.AABB.Y + miny, "top-u" );
                        msg.Write( leaf.AABB.X + leaf.AABB.Width + minx, "right-u" );
                        msg.Write( leaf.AABB.Y + leaf.AABB.Height + miny, "bottom-u" );
                        msg.Write( leaf.Buffer, "content" );
                        sentPixel += leaf.AABB.Width * leaf.AABB.Height;
                    }
                    //Console.WriteLine( sw.Elapsed.TotalSeconds.ToDurationString( ) + " for segmentation and message creation" );

                    if ( changedPixels < width * height * 0.5 )
                        clearcheck = false;
                }

                if ( clearcheck )
                {
                    clearmsg = new NetMessage( SendCodes.WindowContent );
                    clearmsg.Write( Hwnd.ToInt64( ), "hwnd" );
                    clearmsg.Write( true, "clear" );
                    clearmsg.Write( r.Left, "left" );
                    clearmsg.Write( r.Top, "top" );
                    clearmsg.Write( r.Right, "right" );
                    clearmsg.Write( r.Bottom, "bottom" );
                    clearmsg.Write( buffer.ToPNGByteArray( width, height ), "content" );
                }

                if ( clearmsg == null ) clearmsg = msg;
                if ( msg == null ) msg = clearmsg;

                foreach ( var peer in Peers )
                {
                    ++AckNeeded;
                    peer.Send( msg.Data.Count > clearmsg.Data.Count ? clearmsg : msg );
                }

                int sizeChanged = changedPixels * 4;
                int bytes = msg.Data.Count;

                //Console.WriteLine( bytes + " bytes, " + changedPixels + " pixels changed, " + sentPixel + " pixels sent, " + ( bytes / ( float )sizeChanged ).ToString( "0.0%" ) + " from changed pixel update" );
            }

            if ( disposeUpdateBuffer )
                WindowExtract.ExtractDeallocate( updateBuffer );

            if ( lastBuffer != IntPtr.Zero )
                WindowExtract.ExtractDeallocate( lastBuffer );
            lastBuffer = buffer;
            lastWidth = width;
            lastHeight = height;
        }

        /// <summary>
        /// Stops the sharing of this window
        /// </summary>
        public void StopSharing( )
        {
            NetMessage msg = new NetMessage( SendCodes.ShareWindowStopSharer );
            msg.Write( Hwnd.ToInt64( ), "hwnd" );
            foreach ( var peer in Peers )
                peer.Send( msg );
            Peers.Clear( );
        }

        /// <summary>
        /// Adds a peer and sends initial picture
        /// </summary>
        internal void AddPeer( NetConnection conn )
        {
            if ( lastBuffer == IntPtr.Zero ) Update( );

            Peers.Add( conn );

            NetMessage msg = new NetMessage( SendCodes.WindowContent );
            msg.Write( Hwnd.ToInt64( ), "hwnd" );
            msg.Write( true, "clear" );
            RECT r = ClientRect;
            msg.Write( r.Left, "left" );
            msg.Write( r.Top, "top" );
            msg.Write( r.Right, "right" );
            msg.Write( r.Bottom, "bottom" );
            msg.Write( lastBuffer.ToPNGByteArray( lastWidth, lastHeight ), "content" );
            conn.Send( msg );
            ++AckNeeded;
        }

        /// <summary>
        /// Removes a peer
        /// </summary>
        public void RemovePeer( NetConnection conn )
        {
            Peers.Remove( conn );
        }
    }
}
